/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.util;

import java.util.*;
import javax.swing.*;
import ch.trackedbean.loaders.*;

/**
 * {@link javax.swing.ListModel} which uses the {@link DataManager} to obtain it's content.<br>
 * You may want to create a {@link IDataListener} which calls {@link #refresh(boolean)} as soon as something has changed.
 * 
 * @see DataManager
 * @author M. Hautle
 * @param <T> The content type of the list
 * @param <D> The discriminator type
 * @param <K> The key type for the parameter map
 * @param <V> The value type for the parameter map
 */
public class ListManagerModel<T, D, K, V> extends AbstractListModel {
    /** The content type of the list. */
    private final Class<T> type;

    /** Additional string specifying the "subtype" of the lists content (may be null). */
    private D subType;

    /** Additional settings for the list content load (may be null). */
    private Map<K, V> settings;

    /** The current list. */
    private List<T> list = Collections.emptyList();

    /** The comparator to use or null. */
    private Comparator<T> comparator;

    /**
     * Default constructor.
     * 
     * @param type The content type of the list
     * @throws IllegalStateException if the group is already bound
     */
    public ListManagerModel(Class<T> type) {
        this(type, null, null, true);
    }

    /**
     * Default constructor.
     * 
     * @param type The content type of the list
     * @param subType Additional discriminator specifying the "subtype" of the lists content (may be null)
     */
    public ListManagerModel(Class<T> type, D subType) {
        this(type, subType, null, true);
    }

    /**
     * Default constructor.
     * 
     * @param type The content type of the list
     * @param subType Additional discriminator specifying the "subtype" of the lists content (may be null)
     * @param settings Additional settings for the list content load (may be null)
     * @param load True if the content should be loaded now (if true {@link #refresh()} will be called)
     */
    public ListManagerModel(Class<T> type, D subType, Map<K, V> settings, boolean load) {
        this.type = type;
        this.subType = subType;
        this.settings = settings;
        if (load)
            refresh();
    }

    /**
     * Refreshes the list.<br>
     * Calls simply {@link DataManager#getList(Class, String, Map)}.
     */
    public void refresh() {
        refresh(false);
    }

    /**
     * Refreshes the list.<br>
     * Calls simply {@link DataManager#getList(Class, String, Map,boolean)} with the passed <code>foreceLoad</code> flag. is specified.
     * 
     * @param forceLoad True to force a load even when the list exists in the cache
     */
    public void refresh(boolean forceLoad) {
        // TODO async call
        list = DataManager.getList(type, subType, settings, forceLoad);
        if (comparator != null)
            Collections.sort(list, comparator);
        fireContentsChanged(this, -1, -1);
    }

    /**
     * Clears the list.
     */
    @SuppressWarnings("unchecked")
    public void clear() {
        final int size = list.size();
        list = Collections.EMPTY_LIST;
        fireIntervalRemoved(this, 0, size > 0 ? size - 1 : 0);
    }

    /**
     * Removes the given elements from the model.
     * 
     * @param c The collection containing the elements to remove
     * @see java.util.List#removeAll(java.util.Collection)
     */
    public void removeAll(Collection<?> c) {
        list.removeAll(c);
        fireContentsChanged(this, -1, -1);
    }

    /**
     * Removes all other elements from the model.
     * 
     * @param c The collection containing the elements to be retained
     * @see java.util.List#retainAll(java.util.Collection)
     */
    public void retainAll(Collection<?> c) {
        list.retainAll(c);
        fireContentsChanged(this, -1, -1);
    }

    /**
     * Adds the given element at the specified index.
     * 
     * @param index The index
     * @param element The element
     * @see java.util.List#add(int, java.lang.Object)
     */
    public void add(int index, T element) {
        if (comparator != null)
            index = computeIndex(element);
        list.add(index, element);
        fireIntervalAdded(this, index, index);
    }

    /**
     * Adds the given element.
     * 
     * @param e The element
     * @see java.util.List#add(java.lang.Object)
     */
    public void add(T e) {
        add(getSize(), e);
    }

    /**
     * Adds the content of the given collection to the model.
     * 
     * @param c The collection holding the elements to add
     * @see java.util.List#addAll(java.util.Collection)
     */
    public void addAll(Collection<? extends T> c) {
        addAll(getSize(), c);
    }

    /**
     * Adds the content of the given collection to the model, the first element of the collection will be inserted at the given index.
     * 
     * @param index The index
     * @param c The collection holding the elements to add
     * @see java.util.List#addAll(int, java.util.Collection)
     */
    public void addAll(int index, Collection<? extends T> c) {
        if (comparator == null) {
            list.addAll(index, c);
        } else {
            for (T e : c)
                list.add(computeIndex(e), e);
        }
        fireContentsChanged(this, -1, -1);
    }

    /**
     * Checks if the model contains the given element.
     * 
     * @param o The object
     * @return True if the model contains it
     * @see java.util.List#contains(java.lang.Object)
     */
    public boolean contains(Object o) {
        return list.contains(o);
    }

    /**
     * Returns the index of the given object.
     * 
     * @param o The object
     * @return The index or -1 if the element doesen't exists
     * @see java.util.List#indexOf(java.lang.Object)
     */
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    /**
     * Removes the element at the given index.
     * 
     * @param index The index
     * @see java.util.List#remove(int)
     */
    public void remove(int index) {
        list.remove(index);
        fireIntervalRemoved(this, index, index);
    }

    /**
     * Removes the given object
     * 
     * @param o The object to remove
     * @see java.util.List#remove(java.lang.Object)
     */
    public void remove(Object o) {
        final int index = list.indexOf(o);
        if (index > -1)
            remove(index);
    }

    /**
     * Sets the element at the given index.
     * 
     * @param index The index
     * @param element The element
     */
    public void set(int index, T element) {
        if (comparator == null) {
            list.set(index, element);
            fireContentsChanged(this, index, index);
        } else {
            remove(index);
            add(element);
        }
    }

    /**
     * Computes the insertion index for the given element.
     * 
     * @param element The element to add
     * @return The insertion index
     */
    private int computeIndex(T element) {
        final int index = Collections.binarySearch(list, element, comparator);
        return index < 0 ? -index - 1 : index;
    }

    /**
     * Returns the comparator.
     * 
     * @return The comparator or null
     */
    public Comparator<T> getComparator() {
        return comparator;
    }

    /**
     * Sets the comparator to use for this list.
     * 
     * @param comparator The new comparator or null
     */
    public void setComparator(Comparator<T> comparator) {
        this.comparator = comparator;
        if (comparator == null)
            return;
        Collections.sort(list, comparator);
        fireContentsChanged(this, -1, -1);
    }

    /**
     * @return Returns the subType.
     */
    public D getSubType() {
        return subType;
    }

    /**
     * @param subType The subType to set.
     */
    public void setSubType(D subType) {
        this.subType = subType;
    }

    /**
     * @return Returns the settings.
     */
    public Map<K, V> getSettings() {
        return settings;
    }

    /**
     * @param settings The settings to set.
     */
    public void setSettings(Map<K, V> settings) {
        this.settings = settings;
    }

    /**
     * @return Returns the type.
     */
    public Class<T> getType() {
        return type;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T getElementAt(int index) {
        return list.get(index);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getSize() {
        return list.size();
    }
}
